/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/protocol.c 
 * @date        2010-04-05
 * @brief	server_framework
 * @see         dserver
 * @history
 */

#include <dlib/str.h>
#include <dlib/mempool.h>
#include <dlib/dlloader.h>

#include "protocol.h"
#include "monitor.h"

/** global variable in prot_tcp.c */
extern prot_ops_t tcp_ops;
/** global variable in prot_udp.c */
extern prot_ops_t udp_ops;
/** global variable in prot_sctp_stream.c */
extern prot_ops_t sctp_stream_ops;
/** global variable in prot_sctp_dgram.c */
extern prot_ops_t sctp_dgram_ops;

static inline void _protocol_settings(dserver_setting_t *s, dserver_prot_node_t *protocol);
static inline void _protocol_unsettings(dserver_prot_node_t *protocol);

static inline int _protocol_register(dserver_setting_t *s, dserver_prot_node_t *protocol);
static inline void _protocol_unregister(dserver_prot_node_t *protocol);

static inline int _protocol_register_all(dserver_t *dc, dserver_protocol_t *protocols);
static inline void _protocol_unregister_all(dserver_protocol_t *protocols);

static inline void _protocol_settings(dserver_setting_t *s, dserver_prot_node_t *protocol)
{
	int ret;
	long integer;
	char *str;
	dlib_confl_t *confl = s->dserver_confl;

	/* type */
	str = dlib_confl_get_string(confl, protocol->p_prot.p_name, P_TYPE, CONFL_STR_COPY);
	if (str == NULL) {
		protocol->p_prot.p_settings.p_type = PROTOCOL_TCP;
	} else {
		if (str_compare(str, TCP_STRING) == 0) {
			protocol->p_prot.p_settings.p_type = PROTOCOL_TCP;
		} else if (str_compare(str, UDP_STRING == 0)) {
			protocol->p_prot.p_settings.p_type = PROTOCOL_UDP;
		} else if (str_compare(str, SCTP_S_STRING == 0)) {
			protocol->p_prot.p_settings.p_type = PROTOCOL_SCTP_STREAM;
		} else if (str_compare(str, SCTP_D_STRING == 0)) {
			protocol->p_prot.p_settings.p_type = PROTOCOL_SCTP_DGRAM;
		} else {
			protocol->p_prot.p_settings.p_type = PROTOCOL_TCP;
		}
		dfree(str);
	}

	/* custom */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_CUSTOM, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_port = P_DEF_CUSTOM;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_custom = (uint8_t)integer;
	}

	/* host */
	str = dlib_confl_get_string(confl, protocol->p_prot.p_name, P_HOST, CONFL_STR_COPY);
	if (str == NULL) {
		protocol->p_prot.p_settings.p_host = strdup(P_DEF_HOST);
	} else {
		protocol->p_prot.p_settings.p_host = str;
	}

	/* port */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_PORT, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_port = P_DEF_PORT;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_port = (uint16_t)integer;
	}
	if (protocol->p_prot.p_settings.p_port == 0) {
		protocol->p_prot.p_settings.p_port = P_DEF_PORT;
	}

	/* timeout */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_TIMEOUT, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_timeout = P_DEF_TIMEOUT;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_timeout = (uint16_t)integer;
	}
	if (protocol->p_prot.p_settings.p_timeout == 0) {
		protocol->p_prot.p_settings.p_timeout = P_DEF_TIMEOUT;
	}

	/* priority */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_PRIORITY, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_priority = P_DEF_PRIORITY;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_priority = (uint16_t)integer;
	}
	if (protocol->p_prot.p_settings.p_priority == 0) {
		protocol->p_prot.p_settings.p_priority = P_DEF_PRIORITY;
	}

	/* tcp_nodelay */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_TCP_NODELAY, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_tcp_nodelay = P_DEF_TCP_NODELAY;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_tcp_nodelay = (uint8_t)integer;
	}

	/* tcp_keepalive */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_TCP_KEEPALIVE, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_tcp_keepalive = P_DEF_TCP_KEEPALIVE;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_tcp_keepalive = (uint8_t)integer;
	}

	/* max_rx_buf */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_MAX_RX_BUF, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_max_rx_buf = P_DEF_MAX_RX_BUF;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_max_rx_buf = (uint32_t)integer;
	}

	/* tcp_keepalive */
	ret = dlib_confl_get_integer(confl, protocol->p_prot.p_name, P_MAX_TX_BUF, &integer);
	if (ret == -E_CONFL_FIELD_NOTEXIST || ret == -E_CONFL_ITEM_NOTEXIST) {
		protocol->p_prot.p_settings.p_max_tx_buf = P_DEF_MAX_TX_BUF;
	} else if (ret == 0) {
		protocol->p_prot.p_settings.p_max_tx_buf = (uint32_t)integer;
	}

	return;
}

static inline void _protocol_unsettings(dserver_prot_node_t *protocol)
{
	if (protocol != NULL && protocol->p_prot.p_settings.p_host != NULL) {
		dfree(protocol->p_prot.p_settings.p_host);
		protocol->p_prot.p_settings.p_host = NULL;
	}
	return;
}

static inline int _protocol_register(dserver_setting_t *s, dserver_prot_node_t *protocol)
{
	int ret = 0;
	char lib_file[PATH_MAX] = {0};
	void *handler = NULL;
	
	/* library-file parser */
	if (s->protocols_dir != NULL) {
		snprintf(lib_file, PATH_MAX, "%s/lib%s.so", s->protocols_dir,
				protocol->p_prot.p_name);
	} else {
		snprintf(lib_file, PATH_MAX, "lib%s.so", protocol->p_prot.p_name);
	}
	
	/* read the ops of the protocol */
	handler = dlopen(lib_file, RTLD_LAZY);
	protocol->p_handler = handler;
	if (handler == NULL) {
		DLOG_ERR(LOG_MAIN, "dlopen %s error: %s", lib_file, dlerror());
		return -E_FILE_OPEN;
	}
	
	protocol->p_prot.s_ops.p_protocol_load = dlsym(handler, PROTOCOL_LOAD_STR);
	protocol->p_prot.s_ops.p_session_init = dlsym(handler, SESSION_INIT_STR);
	protocol->p_prot.s_ops.p_session_worker = dlsym(handler, SESSION_WORKER_STR);
	protocol->p_prot.s_ops.p_session_exit = dlsym(handler, SESSION_EXIT_STR);
	protocol->p_prot.s_ops.p_protocol_unload = dlsym(handler, PROTOCOL_UNLOAD_STR);

	/* init_protocol */
	if (protocol->p_prot.s_ops.p_protocol_load != NULL) {
		ret = (*protocol->p_prot.s_ops.p_protocol_load)(s, &protocol->p_prot);
		if (ret < 0) {
			DLOG_ERR(LOG_MAIN, "protocol %s init error.", protocol->p_prot.p_name);
			return ret;
		}
	} else {
		DLOG_DEBUG(LOG_MAIN, "protocol %s without init function.",
				protocol->p_prot.p_name);	
	}

	/* set protocol-related operations */
	if (protocol->p_prot.p_settings.p_type == PROTOCOL_TCP) {
		protocol->p_prot.p_ops = tcp_ops;
	} else if (protocol->p_prot.p_settings.p_type == PROTOCOL_UDP) {
		protocol->p_prot.p_ops = udp_ops;
	} else if (protocol->p_prot.p_settings.p_type == PROTOCOL_SCTP_STREAM) {
		protocol->p_prot.p_ops = sctp_stream_ops;
	} else if (protocol->p_prot.p_settings.p_type == PROTOCOL_SCTP_DGRAM) {
		protocol->p_prot.p_ops = sctp_dgram_ops;
	}
	
	DLOG_INFO(LOG_MAIN, "[PROTOCOL]register_protocol %s successfully.",
			protocol->p_prot.p_name);

	return RET_SUCCESS;
}

static inline void _protocol_unregister(dserver_prot_node_t *protocol)
{
	if (protocol == NULL) {
		return;
	}
	if (protocol->p_prot.s_ops.p_protocol_unload != NULL) {
		(*protocol->p_prot.s_ops.p_protocol_unload)(&protocol->p_prot);
	} else {
		DLOG_DEBUG(LOG_MAIN, "protocol %s without exit function.",
				protocol->p_prot.p_name);
	}
	if (protocol->p_handler != NULL) {
		dlclose(protocol->p_handler);
	}
	return;
}

static inline int _protocol_register_all(dserver_t *dc, dserver_protocol_t *protocols)
{
	int ret = 0;
	int len = 0;
	char *prot_begin = NULL;
	char *prot_end = NULL;
	
	dserver_prot_node_t *protocol = NULL;
	dserver_setting_t *s = dc->settings;
	
	if (s->protocols == NULL || s->dserver_confl == NULL)
		return -E_ARGS_ILLEGAL;
	
	prot_begin = s->protocols;
	do {
		protocol = dmalloc(sizeof(*protocol));
		protocol->p_prot.p_sfd = -1;
		protocol->p_prot.p_main_event = NULL;
		
		prot_end = strchr(prot_begin, ',');
		if (prot_end == NULL) {
			len = strlen(prot_begin);
			protocol->p_prot.p_name = dmalloc(len + 1);
			memcpy(protocol->p_prot.p_name, prot_begin, len);
			str_trim(protocol->p_prot.p_name);
			prot_begin = NULL;
		} else {
			len = prot_end - prot_begin;
			protocol->p_prot.p_name = dmalloc(len + 1);
			memcpy(protocol->p_prot.p_name, prot_begin, len);
			str_trim(protocol->p_prot.p_name);
			prot_begin = prot_end + 1;
		}

		/* initialize protocol_settings, it's needed by reactor */
		_protocol_settings(s, protocol);
		
		/* initialize protocol_ops and do protocol_init */
		ret = _protocol_register(s, protocol);
		if (ret < 0) {
			DLOG_WARNING(LOG_MAIN, "register protocol %s error[%d], discard.",
					protocol->p_prot.p_name, ret);
			_protocol_unsettings(protocol);
			dfree(protocol->p_prot.p_name);
			dfree(protocol);
			continue;
		} else {
			dlib_lock_lock(&protocols->p_lock);
			dlib_atomic_inc(&protocols->p_nr);
			LIST_ADD_TAIL(&protocols->p_head, &protocol->p_node);
			dlib_lock_unlock(&protocols->p_lock);
		}
	} while (prot_begin != NULL);
	
	return RET_SUCCESS;
}

static inline void _protocol_unregister_all(dserver_protocol_t *protocols)
{
	dserver_prot_node_t *tpos = NULL;
	list_node_t *pos = NULL;
	list_node_t *n = NULL;
	
	if (protocols == NULL || dlib_atomic_test(&protocols->p_nr, 0)) {
		return;
	}

	dlib_lock_lock(&protocols->p_lock);
	LIST_FOREACH_ENTRY_SAFE(tpos, pos, n, &protocols->p_head, p_node) {
		dlib_atomic_dec(&protocols->p_nr);
		LIST_DEL(&tpos->p_node);

		_protocol_unregister(tpos);
		_protocol_unsettings(tpos);
		dfree(tpos->p_prot.p_name);
		dfree(tpos);
	}
	dlib_lock_unlock(&protocols->p_lock);
	
	return;
}

int protocol_init(dserver_t *dc)
{
	int ret = RET_SUCCESS;
	dserver_protocol_t *protocols;
	
	DLOG_DEBUG(LOG_MAIN, "dserver enters in protocol_init function.");
	if (dc == NULL) {
		DLOG_ERR(LOG_MAIN, "protocol_init parameters error.");
		return -E_ARGS_ILLEGAL;
	}

	protocols = dmalloc(sizeof(*protocols));
	dc->protocols = protocols;
	
	dlib_lock_init(&protocols->p_lock);
	dlib_atomic_init(&protocols->p_nr, 0);
	LIST_HEAD_INIT(&protocols->p_head);

	ret = monitor_init(dc, protocols);
	if (ret < 0) {
		DLOG_ERR(LOG_MAIN, "monitor_init error, unexpected.");
		return ret;
	}
	
	ret = _protocol_register_all(dc, protocols);
	if (ret < 0) {
		DLOG_ERR(LOG_MAIN, "protocol_register error, unexpected.");
		return ret;
	}

	return ret;
}

void protocol_exit(dserver_t *dc)
{
	dserver_protocol_t *protocols;
	
	DLOG_DEBUG(LOG_MAIN, "dserver enters in protocol_exit function.");
	if (dc == NULL || dc->protocols == NULL) return;

	protocols = dc->protocols;
	_protocol_unregister_all(protocols);
	dlib_lock_exit(&protocols->p_lock);
	monitor_exit(protocols->p_monitor);
	dfree(protocols);

	return;
}
